オレオレ言語の字句解析 for いろんなプラットフォームについて
概要
Scalaでパーサコンビネータ作った以外で、ちゃんと言語の字句解析を行なったことがないのでやってみている。
参考
bison/flex(yacc/lex)について
http://hp.vector.co.jp/authors/VA022047/linux/bison.html
flex man
http://dinosaur.compilertools.net/flex/manpage.html
道具と使用場所に関する分析
やるべきことは
1.字句解析機を出力する
2.使う
なのだけれど、実際に使われる環境はどんななんだろうか。字句解析が行われるのが確定している環境は、
・Unity
・ブラウザ
という感じで、ようはライブラリにできると良さそう。
適当にDocker上のLinuxから吐く。最終的にブラウザで使うならwasmにすればよかろう。c to wasmはどっかあった気がする。
そうでなくてもLLVM関連で出力できそう。
Unity向けであればC#のILか、やはりライブラリを出力したいところ。これにはどういう手段があるだろうか。
パースするべきものの設定とflexの実行
こんな感じの.lファイルをつくる。内容はmanに書いてあったやつ。
%option noyywrap
/* scanner for a toy Pascal-like language */
%{
/* need this for the call to atof() below */
#include <math.h>
%}
digit [0-9]
alpha [A-Za-z_]*
alpha_num ({alpha}|{digit})
/*
こういうコメントは存在できるのか。
*/
%%
{digit}+ {
printf( "An integer: %s (%d)\n", yytext, atoi( yytext ) );
}
{digit}+"."{digit}* {
printf( "A float: %s (%g)\n", yytext, atof( yytext ) );
}
if|then|begin|end|procedure|function {
printf( "A keyword: %s\n", yytext );
}
{alpha} printf( "An identifier: %s\n", yytext );
"+"|"-"|"*"|"/" printf( "An operator: %s\n", yytext );
"{"[^}\n]*"}" /* eat up one-line comments */
[ \t\n]+ /* eat up whitespace */
. printf( "Unrecognized character: %s\n", yytext );
%%
main( argc, argv )
int argc;
char **argv;
{
++argv, --argc; /* skip over program name */
if ( argc > 0 ) {
yyin = fopen( argv[0], "r" );
} else {
yyin = stdin;
}
yylex();
}
うん、まだ全然わかんないや。とりあえずmanのいうとおりに進める。
flex parsel.l
で、lex.yy.cというcコードが出力される。
gcc lex.yy.c
それをgccとかでコンパイル。Macで実行するとclangが使われる。
特に名前を指定してないので、コンパイル結果としてa.outファイルが出力される。
ここでまあ、%option noyywrap とかを.lファイルの行頭に書いておくと、flexさんがyywrap(n)というdefineを追加してくれる。
今はまず字句解析の結果だけが欲しいので、これで動かす。
どんなものがパースされてどうなるんだろう
で、manに書いてあったサンプルがpascal-likeとのことだったので、雑にhello world + アルファを食わせてみる。
hello.pas
program Hello;
begin
writeln('Hello, world.');
Sentinel =0.0;
end.
で、
./a.out hello.pas
とか動かすと、
An identifier: program
An identifier: Hello
Unrecognized character: ;
A keyword: begin
An identifier: writeln
Unrecognized character: (
Unrecognized character: '
An identifier: Hello
Unrecognized character: ,
An identifier: world
Unrecognized character: .
Unrecognized character: '
Unrecognized character: )
Unrecognized character: ;
An identifier: Sentinel
Unrecognized character: =
A float: 0.0 (0)
Unrecognized character: ;
A keyword: end
Unrecognized character: .
、、、うん、まあ、なんていうか、うん。なるほど。
pascal likeだからな。
ちょっと改良して構文と内容の関連性がわかった。
で、やりたいことを実装に移す。
Emit2501形式では、時間経過的な駆動のために必要なパラメータと、実際にメソッドを着火するための機構が連結してある。
実行系もサクッと作ってしまったので、まあ、なんというか汚い。
これを綺麗にしていく第一歩として、パーサを実装していろんな環境で使えるようにしてみよう、という感じ。
とりあえずだいたいの部分はlexで字句解析できるようになった。
やったぜ!